﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using System.Diagnostics;
using System.ComponentModel;
using System.Runtime.Serialization;
using System.Xml;
using MoneyWentWhere.DataModel.Binding;
using MoneyWentWhere.DataModel.Storage;
using MoneyWentWhere.Utils.Storage;
using MoneyWentWhere.Utils;
using System.Windows;

namespace MoneyWentWhere.DataModel
{
    [DataContract(Name = "MoneyDataSet")]
    public class MoneyDataSet : 
        IAfterLoad, IHasStorageInfo, 
        INotifyPropertyChanged, IExtensibleDataObject, IDisposable
    {
        // Direct people viewing the XML in the editor to the program site 
        [DataMember]
        private String ProgramWebsite = "http://code.google.com/p/moneywentwhere/";

        [DataMember(Name = "Accounts")]
        public AccountList Accounts;

        [DataMember(Name = "DefaultCurrency")]
        public Currency DefaultCurrency = new Currency("USD");

        // Currency list. Perhaps better to keep it with global data
        [DataMember(Name = "Currencies")]
        public readonly CurrencyList Currencies = new CurrencyList();

        // Categories 
        [DataMember(Name = "Categories")]
        public readonly CategorySet Categories = new CategorySet();

        public MoneyDataSet()
        {
            Accounts = new AccountList(this);
        }

        // All objects with Guids
        public Dictionary<Guid, object> ObjectByGuid
        {
            get { return Lazy.Get<Dictionary<Guid, object>>(ref _objectByGuid); }
        }
        Dictionary<Guid, object> _objectByGuid;

        public FileStorageInfo StorageInfo
        {
            get { return Lazy.Get(ref _storageInfo); }
            set { _storageInfo = value; }
        }
        FileStorageInfo _storageInfo;

        public Account CreateAccount(String name)
        {
            return CreateAccount(name, 0, -1);
        }

        public Account CreateAccount(String name, decimal openingBalance)
        {
            return CreateAccount(name, openingBalance, -1);
        }

        /// <summary>
        /// Create a new account. Adds an opening balance transaction to it.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        public Account CreateAccount(String name, decimal openingBalance, int index)
        {
            Trace.Write("Creating account @" + index);

            Account a = new Account(this, name);

            if (index == -1) { Accounts.Add(a); }
            else { Accounts.Insert(index, a); }

            Transaction t = a.CreateTransaction(openingBalance, TransactionKind.Reconcile);
            t.Comment = "Opening Balance";

            return a;
        }

        /// <summary>
        /// Get a uniquely identified object by its guid
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="guid"></param>
        /// <returns></returns>
        public T GetObject<T>(Guid guid) where T : class
        {
            object o;
            if (ObjectByGuid.TryGetValue(guid, out o))
            {
                return o as T;                
            }
            return null;
        }

        internal void StoreObject(Guid guid, object o)
        {
            if (guid == Guid.Empty) { throw new ArgumentException("Empty guid"); }
            ObjectByGuid.Add(guid, o);
        }

        /// <summary>
        /// Get the exchange rate before the given date, -1 if unavailable
        /// xr = toAmount / fromAmount 
        /// </summary>
        /// <param name="from"></param>
        /// <param name="to"></param>
        /// <param name="beforeDate"></param>
        /// <returns></returns>
        public decimal GetExchangeRate(Currency from, Currency to, DateTime beforeDate)
        {
            // TODO: write in linq
            List<Transaction> ts = new List<Transaction>();
            foreach(Account a in Accounts)
            {
                if (a.Currency != from) { continue; }
                foreach(Transaction t in a.Transactions)
                {
                    if (t.Date >= beforeDate) { continue; }
                    if (t.TransferToTx == null) { continue; }
                    if (t.TransferToTx.Account.Currency != to) { continue; }
                    ts.Add(t);
                }
            }

            // Sort to find the latest one
            Transaction latest = null;
            foreach(Transaction t in ts)
            {
                if (latest == null || t.Date > latest.Date) { latest = t; }
            }

            // If there is no exchange rate
            if (latest == null) { return -1; }

            return Currency.ExchangeRate(latest.Amount, latest.TransferToTx.Amount);
        }

        #region Load/Save/Close

        public static MoneyDataSet Load(String filename, String password = "")
        {
            FileFormat<MoneyDataSet> format = MwwFileFormats.GetOpenFileFormat(filename);
            return format.Load(filename, password);
        }

        public void Save()
        {
            var format = (FileFormat<MoneyDataSet>)StorageInfo.DataFormat;
            format.Save(this);
        }

        public void CloseFile()
        {
            // Some formats keep the file open and require closing
            IDisposable extraInfo = StorageInfo.ExtraFileInfo as IDisposable;
            IDisposable dataFormat = StorageInfo.DataFormat as IDisposable;

            if (extraInfo != null) { extraInfo.Dispose(); }
            if (dataFormat != null) { dataFormat.Dispose(); }

            StorageInfo.ExtraFileInfo = null;
            StorageInfo.DataFormat = null;
        }

        #endregion

        #region Common Serialization / Notification methods
        public event PropertyChangedEventHandler PropertyChanged;

        void OnPropertyChanged(String name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }

        [Browsable(false)]
        public ExtensionDataObject ExtensionData { get; set; }

        void IAfterLoad.AfterLoad()
        {
            Accounts.DataSet = this;
            foreach (var ac in Accounts)
            {
                if (!ObjectByGuid.ContainsKey(ac.Guid))
                {
                    ObjectByGuid.Add(ac.Guid, ac);
                }

                ac.DataSet = this;
                ac.Transactions.Account = ac;

                decimal acBalance = 0;
                foreach (Transaction tx in ac.Transactions)
                {
                    // In case of transfers, tx may be already added
                    if (!ObjectByGuid.ContainsKey(tx.Guid))
                    {
                        ObjectByGuid.Add(tx.Guid, tx);
                    }
                    
                    tx.Account = ac;

                    acBalance += tx.Amount;
                }
            }
        }

        #endregion


        void IDisposable.Dispose()
        {
            CloseFile();
        }
    }
}
